﻿using ImageK.Gui;
using ImageK.Macros;
using ImageK.Measure;
using ImageK.Plugin.Filter;
using ImageK.Process;
using ImageK.Text;
using ImageK.Util;
using System;
using System.Collections.Generic;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static ImageK.Process.AutoThresholder;

namespace ImageK.Plugin
{
    /** This plugin implements the Image/Show Info command. */
    public class ImageInfo : PlugIn
    {
        public void run(String arg)
        {
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp == null)
                showInfo();
            else
            {
                String info = getImageInfo(imp);
                if (info.Contains("----"))
                    showInfo(imp, info, 450, 700);
                else
                {
                    int inc = info.Contains("No selection") ? 0 : 130;
                    showInfo(imp, info, 400, 600 + inc);
                }
            }
        }

        private void showInfo()
        {
            String s = new String("");
            if (IJ.GetInstance() != null)
                s += IJ.GetInstance().getInfo() + "\n \n";
            s += "No images are open\n";
            Size screen = IJ.getScreenSize();
            s += "ImageJ home: " + IJ.getDir("imagek") + "\n";
            s += ".Net version: " + Environment.Version + "\n";
            s += "Screen size: " + screen.Width + "x" + screen.Height + "\n";
            s += "GUI scale: " + IJ.d2s(Prefs.getGuiScale(), 2) + "\n";
            //s += "Active window: "+WindowManager.getActiveWindow()+"\n";
            String path = Prefs.getCustomPropsPath();
            if (path != null)
                s += "*Custom properties*: " + path + "\n";
            path = Prefs.getCustomPrefsPath();
            if (path != null)
                s += "*Custom preferences*: " + path + "\n";
            //if (IJ.isMacOSX()) {
            //	String time = " ("+ImageWindow.setMenuBarTime+"ms)";
            //	s += "SetMenuBarCount: "+Menus.setMenuBarCount+time+"\n";
            //}
            new TextWindow("Info", s, 600, 300);
        }

        public string getImageInfo(ImagePlus imp)
        {
            ImageProcessor ip = imp.getProcessor();
            String infoProperty = null;
            bool isStack = imp.getStackSize() > 1;
            if (isStack || imp.hasImageStack())
            {
                ImageStack stack = imp.getStack();
                String label = stack.getSliceLabel(imp.getCurrentSlice());
                if (label != null && label.IndexOf('\n') > 0)
                    infoProperty = label;
            }

            //todo:
            // if (infoProperty == null || (isStack && (imp.getStack() is ListVirtualStack))) {
            //     infoProperty = (String)imp.getProperty("Info");
            //     if (infoProperty == null)
            //         infoProperty = getExifData(imp);
            // }
            if (imp.getProp("HideInfo") == null)
            {
                String properties = getImageProperties(imp);
                if (properties != null)
                {
                    if (infoProperty != null)
                        infoProperty = properties + "\n" + infoProperty;
                    else
                        infoProperty = properties;
                }
            }

            String info = getInfo(imp, ip);
            if (infoProperty != null)
                return infoProperty + "--------------------------------------------\n" + info;
            else
                return info;
        }

        public String getExifData(ImagePlus imp)
        {
            return null;
            // FileInfo fi = imp.getOriginalFileInfo();
            // if (fi == null)
            //     return null;
            // String directory = fi.directory;
            // String name = fi.fileName;
            // if (directory == null)
            //     return null;
            // if ((name == null || name.Equals("")) && imp.getStack().isVirtual())
            //     name = imp.getStack().getSliceLabel(imp.getCurrentSlice());
            // if (name == null || !(name.EndsWith("jpg") || name.EndsWith("JPG")))
            //     return null;
            // String path = directory + name;
            // String metadata = null;
            // try
            // {
            //     Class c = IJ.getClassLoader().loadClass("Exif_Reader");
            //     if (c == null) return null;
            //     String methodName = "getMetadata";
            //     Class[] argClasses = new Class[1];
            //     argClasses[0] = methodName.getClass();
            //     Method m = c.getMethod("getMetadata", argClasses);
            //     Object[] args = new Object[1];
            //     args[0] = path;
            //     Object obj = m.invoke(null, args);
            //     metadata = obj != null ? obj.toString() : null;
            // }
            // catch (Exception e)
            // {
            //     return null;
            // }
            //
            // if (metadata != null && !metadata.startsWith("Error:"))
            //     return metadata;
            // else
            //     return null;
        }

        private String getInfo(ImagePlus imp, ImageProcessor ip)
        {
            String s = new String("");
            if (IJ.GetInstance() != null)
                s += IJ.GetInstance().getInfo() + "\n \n";
            s += "Title: " + imp.getTitle() + "\n";
            Calibration cal = imp.getCalibration();
            int stackSize = imp.getStackSize();
            int channels = imp.getNChannels();
            int slices = imp.getNSlices();
            int frames = imp.getNFrames();
            int digits = imp.getBitDepth() == 32 ? 4 : 0;
            int dp, dp2;
            bool nonUniformUnits = !cal.getXUnit().Equals(cal.getYUnit());
            string xunit = cal.getXUnit();
            string yunit = cal.getYUnit();
            string zunit = cal.getZUnit();
            if (cal.scaled())
            {
                string xunits = cal.getUnits();
                string yunits = xunits;
                string zunits = xunits;
                if (nonUniformUnits)
                {
                    xunits = xunit;
                    yunits = yunit;
                    zunits = zunit;
                }

                double pw = imp.getWidth() * cal.pixelWidth;
                double ph = imp.getHeight() * cal.pixelHeight;
                s += "Width:  " + d2s(pw) + " " + xunits + " (" + imp.getWidth() + ")\n";
                s += "Height:  " + d2s(ph) + " " + yunits + " (" + imp.getHeight() + ")\n";
                if (slices > 1)
                {
                    double pd = slices * cal.pixelDepth;
                    s += "Depth:  " + d2s(pd) + " " + zunits + " (" + slices + ")\n";
                }

                s += "Size:  " + ImageWindow.getImageSize(imp) + "\n";
                double xResolution = 1.0 / cal.pixelWidth;
                double yResolution = 1.0 / cal.pixelHeight;
                if (xResolution == yResolution)
                    s += "Resolution:  " + d2s(xResolution) + " pixels per " + xunit + "\n";
                else
                {
                    s += "X Resolution:  " + d2s(xResolution) + " pixels per " + xunit + "\n";
                    s += "Y Resolution:  " + d2s(yResolution) + " pixels per " + yunit + "\n";
                }
            }
            else
            {
                s += "Width:  " + imp.getWidth() + " pixels\n";
                s += "Height:  " + imp.getHeight() + " pixels\n";
                if (stackSize > 1)
                    s += "Depth:  " + slices + " pixels\n";
                s += "Size:  " + ImageWindow.getImageSize(imp) + "\n";
            }

            if (stackSize > 1)
            {
                String vunit = cal.getUnit() + "^3";
                if (nonUniformUnits)
                    vunit = "(" + xunit + " x " + yunit + " x " + zunit + ")";
                s += "Voxel size: " + d2s(cal.pixelWidth) + "x" + d2s(cal.pixelHeight) + "x" + d2s(cal.pixelDepth) +
                     " " + vunit + "\n";
            }
            else
            {
                String punit = cal.getUnit() + "^2";
                if (nonUniformUnits)
                    punit = "(" + xunit + " x " + yunit + ")";
                dp = Tools.getDecimalPlaces(cal.pixelWidth, cal.pixelHeight);
                s += "Pixel size: " + d2s(cal.pixelWidth) + "x" + d2s(cal.pixelHeight) + " " + punit + "\n";
            }

            s += "ID: " + imp.getID() + "\n";
            int type = imp.getType();
            switch (type)
            {
                case ImagePlus.GRAY8:
                    s += "Bits per pixel: 8 ";
                    String lut = "LUT";
                    if (imp.getProcessor().isColorLut())
                        lut = "color " + lut;
                    else
                        lut = "grayscale " + lut;
                    if (imp.isInvertedLut())
                        lut = "inverting " + lut;
                    s += "(" + lut + ")\n";
                    if (imp.getNChannels() > 1)
                        s += displayRanges(imp);
                    else
                        s += "Display range: " + (int)ip.getMin() + "-" + (int)ip.getMax() + "\n";
                    break;
                case ImagePlus.GRAY16:
                case ImagePlus.GRAY32:
                    if (type == ImagePlus.GRAY16)
                    {
                        String sign = cal.isSigned16Bit() ? "signed" : "unsigned";
                        s += "Bits per pixel: 16 (" + sign + ")\n";
                    }
                    else
                        s += "Bits per pixel: 32 (float)\n";

                    if (imp.getNChannels() > 1)
                        s += displayRanges(imp);
                    else
                    {
                        s += "Display range: ";
                        double min = ip.getMin();
                        double max = ip.getMax();
                        if (cal.calibrated())
                        {
                            min = cal.getCValue((int)min);
                            max = cal.getCValue((int)max);
                        }

                        s += d2s(min) + " - " + d2s(max) + "\n";
                    }

                    break;
                case ImagePlus.COLOR_256:
                    s += "Bits per pixel: 8 (color LUT)\n";
                    break;
                case ImagePlus.COLOR_RGB:
                    s += "Bits per pixel: 32 (RGB)\n";
                    break;
            }

            String lutName = imp.getProp(LUT.nameKey);
            if (lutName != null)
                s += "LUT name: " + lutName + "\n";
            double interval = cal.frameInterval;
            double fps = cal.fps;
            if (stackSize > 1)
            {
                ImageStack stack = imp.getStack();
                int slice = imp.getCurrentSlice();
                String number = slice + "/" + stackSize;
                String label = stack.getSliceLabel(slice);
                if (label != null && label.Contains("\n"))
                    label = stack.getShortSliceLabel(slice);
                if (label != null && label.Length > 0)
                    label = " (" + label + ")";
                else
                    label = "";
                if (imp.getNFrames() > 1 || interval > 0.0 || fps != 0.0)
                {
                    s += "Frame: " + number + label + "\n";
                    if (fps != 0.0)
                    {
                        String sRate = Math.Abs(fps - Math.Round(fps)) < 0.00001 ? IJ.d2s(fps, 0) : IJ.d2s(fps, 5);
                        s += "Frame rate: " + sRate + " fps\n";
                    }
                    else
                        s += "Frame interval: " +
                             ((int)interval == interval ? IJ.d2s(interval, 0) : IJ.d2s(interval, 5)) + " " +
                             cal.getTimeUnit() + "\n";
                }
                else
                    s += "Image: " + number + label + "\n";

                if (imp.isHyperStack())
                {
                    if (channels > 1)
                        s += "  Channel: " + imp.getChannel() + "/" + channels + "\n";
                    if (slices > 1)
                        s += "  Slice: " + imp.getSlice() + "/" + slices + "\n";
                    if (frames > 1)
                        s += "  Frame: " + imp.getFrame() + "/" + frames + "\n";
                }

                if (imp.isComposite())
                {
                    if (!imp.isHyperStack() && channels > 1)
                        s += "  Channels: " + channels + "\n";
                    String mode = ((CompositeImage)imp).getModeAsString();
                    s += "  Composite mode: \"" + mode + "\"\n";
                }

                if (stack.isVirtual())
                {
                    String stackType = "virtual";
                    if (stack is AVI_Reader)
                        stackType += " (AVI Reader)";
                    if (stack is FileInfoVirtualStack)
                        stackType += " (FileInfoVirtualStack)";
                    //todo:
                    // if (stack is ListVirtualStack)
                    //     stackType += " (ListVirtualStack)";
                    s += "Stack type: " + stackType + "\n";
                }
            }
            else if (imp.hasImageStack())
            {
                // one image stack
                String label = imp.getStack().getShortSliceLabel(1);
                if (label != null && label.Length > 0)
                    s += "Image: 1/1 (" + label + ")\n";
            }

            if (imp.isLocked())
                s += "**Locked**\n";
            if (ip.getMinThreshold() == ImageProcessor.NO_THRESHOLD)
                s += "No threshold\n";
            else
            {
                double lower = ip.getMinThreshold();
                double upper = ip.getMaxThreshold();
                String uncalibrated = "";
                if (cal.calibrated())
                {
                    uncalibrated = " (" + (int)lower + "-" + (int)upper + ")";
                    lower = cal.getCValue((int)lower);
                    upper = cal.getCValue((int)upper);
                }

                int lutMode = ip.getLutUpdateMode();
                String mode = "red";
                switch (lutMode)
                {
                    case ImageProcessor.BLACK_AND_WHITE_LUT:
                        mode = "B&W";
                        break;
                    case ImageProcessor.NO_LUT_UPDATE:
                        mode = "invisible";
                        break;
                    case ImageProcessor.OVER_UNDER_LUT:
                        mode = "over/under";
                        break;
                }

                s += "Threshold: " + d2s(lower) + "-" + d2s(upper) + uncalibrated + " (" + mode + ")\n";
            }

            ImageCanvas ic = imp.getCanvas();
            double mag = ic != null ? ic.getMagnification() : 1.0;
            if (mag != 1.0)
                s += "Magnification: " + IJ.d2s(mag, 2) + "\n";
            if (ic != null)
                s += "ScaleToFit: " + ic.getScaleToFit() + "\n";


            String valueUnit = cal.getValueUnit();
            if (cal.calibrated())
            {
                s += " \n";
                int curveFit = cal.getFunction();
                s += "Calibration function: ";
                if (curveFit == Calibration.UNCALIBRATED_OD)
                    s += "Uncalibrated OD\n";
                else if (curveFit == Calibration.CUSTOM)
                    s += "Custom lookup table\n";
                else
                    s += CurveFitter.fList[curveFit] + "\n";
                double[] c = cal.getCoefficients();
                if (c != null)
                {
                    s += "  a: " + IJ.d2s(c[0], 6) + "\n";
                    s += "  b: " + IJ.d2s(c[1], 6) + "\n";
                    if (c.Length >= 3)
                        s += "  c: " + IJ.d2s(c[2], 6) + "\n";
                    if (c.Length >= 4)
                        s += "  c: " + IJ.d2s(c[3], 6) + "\n";
                    if (c.Length >= 5)
                        s += "  c: " + IJ.d2s(c[4], 6) + "\n";
                }

                s += "  Unit: \"" + valueUnit + "\"\n";
            }
            else if (valueUnit != null && !valueUnit.Equals("Gray Value"))
            {
                s += "Calibration function: None\n";
                s += "  Unit: \"" + valueUnit + "\"\n";
            }
            else
                s += "Uncalibrated\n";

            IO.FileInfo fi = imp.getOriginalFileInfo();
            if (fi != null)
            {
                if (fi.url != null && !fi.url.Equals(""))
                    s += "URL: " + fi.url + "\n";
                else
                {
                    string userDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
                    String defaultDir = (fi.directory == null || fi.directory.Length == 0)
                        ? userDirectory
                        : "";
                    if (defaultDir.Length > 0)
                    {
                        defaultDir = defaultDir.Replace("\\\\", "/");
                        defaultDir += "/";
                    }

                    s += "Path: " + defaultDir + fi.getFilePath() + "\n";
                }
            }

            ImageWindow win = imp.getWindow();
            if (win != null)
            {
                Point loc = win.Location;
                Rectangle bounds = GUI.getScreenBounds(win);
                s += "Screen location: " + (loc.X - bounds.X) + "," + (loc.Y - bounds.Y) + " (" + bounds.Width + "x" +
                     bounds.Height + ")\n";
            }

            // if (IJ.isMacOSX())
            // {
            //     String time = " (" + ImageWindow.setMenuBarTime + "ms)";
            //     s += "SetMenuBarCount: " + Menus.setMenuBarCount + time + "\n";
            // }

            String zOrigin = stackSize > 1 || cal.zOrigin != 0.0 ? "," + d2s(cal.zOrigin) : "";
            String origin = d2s(cal.xOrigin) + "," + d2s(cal.yOrigin) + zOrigin;
            if (!origin.Equals("0,0") || cal.getInvertY())
                s += "Coordinate origin:  " + origin + "\n";
            if (cal.getInvertY())
                s += "Inverted y coordinates\n";

            String pinfo = imp.getPropsInfo();
            if (!pinfo.Equals("0"))
                s += "Properties: " + pinfo + "\n";
            else
                s += "No properties\n";

            Overlay overlay = imp.getOverlay();
            if (overlay != null)
            {
                int n = overlay.size();
                String elements = n == 1 ? " element" : " elements";
                String selectable = overlay.isSelectable() ? " selectable " : " non-selectable ";
                String hidden = imp.getHideOverlay() ? " (hidden)" : "";
                s += "Overlay: " + n + selectable + elements + hidden + "\n";
            }
            else
                s += "No overlay\n";

            Interpreter interp = Interpreter.getInstance();
            if (interp != null)
                s += "Macro is running" + (Interpreter.isBatchMode() ? " in batch mode" : "") + "\n";

            Roi roi = imp.getRoi();
            if (roi == null)
            {
                if (cal.calibrated())
                    s += " \n";
                s += "No selection\n";
            }
            else if (roi is RotatedRectRoi) {
                s += "\nRotated rectangle selection\n";
                double[] p = ((RotatedRectRoi)roi).getParams();
                double dx = p[2] - p[0];
                double dy = p[3] - p[1];
                double major = Math.Sqrt(dx * dx + dy * dy);
                s += "  Length: " + IJ.d2s(major, 2) + "\n";
                s += "  Width: " + IJ.d2s(p[4], 2) + "\n";
                s += "  X1: " + IJ.d2s(p[0], 2) + "\n";
                s += "  Y1: " + IJ.d2s(p[1], 2) + "\n";
                s += "  X2: " + IJ.d2s(p[2], 2) + "\n";
                s += "  Y2: " + IJ.d2s(p[3], 2) + "\n";
            } 
            else if (roi is EllipseRoi) 
            {
                s += "\nElliptical selection\n";
                double[] p = ((EllipseRoi)roi).getParams();
                double dx = p[2] - p[0];
                double dy = p[3] - p[1];
                double major = Math.Sqrt(dx * dx + dy * dy);
                s += "  Major: " + IJ.d2s(major, 2) + "\n";
                s += "  Minor: " + IJ.d2s(major * p[4], 2) + "\n";
                s += "  X1: " + IJ.d2s(p[0], 2) + "\n";
                s += "  Y1: " + IJ.d2s(p[1], 2) + "\n";
                s += "  X2: " + IJ.d2s(p[2], 2) + "\n";
                s += "  Y2: " + IJ.d2s(p[3], 2) + "\n";
                s += "  Aspect ratio: " + IJ.d2s(p[4], 2) + "\n";
            } 
            else
            {
                s += " \n";
                s += roi.getTypeAsString() + " Selection";
                String points = null;
                if (roi is PointRoi) {
                    int npoints = ((PolygonRoi)roi).getNCoordinates();
                    String suffix = npoints > 1 ? "s)" : ")";
                    points = " (" + npoints + " point" + suffix;
                }
                String name = roi.getName();
                if (name != null)
                {
                    s += " (\"" + name + "\")";
                    if (points != null) s += "\n " + points;
                }
                else if (points != null)
                    s += points;

                s += "\n";
                if (roi is Line) {
                    Line line = (Line)roi;
                    s += "  X1: " + IJ.d2s(cal.getX(line.x1d)) + "\n";
                    s += "  Y1: " + IJ.d2s(cal.getY(line.y1d, imp.getHeight())) + "\n";
                    s += "  X2: " + IJ.d2s(cal.getX(line.x2d)) + "\n";
                    s += "  Y2: " + IJ.d2s(cal.getY(line.y2d, imp.getHeight())) + "\n";
                } 
                else
                {
                    RectangleF r = roi.getFloatBounds();
                    int decimals = r.X == (int)r.X && r.Y == (int)r.Y && r.Width == (int)r.Width &&
                                   r.Height == (int)r.Height
                        ? 0
                        : 2;
                    if (cal.scaled())
                    {
                        s += "  X: " + IJ.d2s(cal.getX(r.X)) + " (" + IJ.d2s(r.X, decimals) + ")\n";
                        s += "  Y: " + IJ.d2s(cal.getY(r.Y, imp.getHeight())) + " (" + IJ.d2s(yy(r.Y, imp), decimals) +
                             ")\n";
                        s += "  Width: " + IJ.d2s(r.Width * cal.pixelWidth) + " (" + IJ.d2s(r.Width, decimals) + ")\n";
                        s += "  Height: " + IJ.d2s(r.Height * cal.pixelHeight) + " (" + IJ.d2s(r.Height, decimals) +
                             ")\n";
                    }
                    else
                    {
                        s += "  X: " + IJ.d2s(r.X, decimals) + "\n";
                        s += "  Y: " + IJ.d2s(yy(r.Y, imp), decimals) + "\n";
                        s += "  Width: " + IJ.d2s(r.Width, decimals) + "\n";
                        s += "  Height: " + IJ.d2s(r.Height, decimals) + "\n";
                    }
                }
            }

            return s;
        }

        private String displayRanges(ImagePlus imp)
        {
            LUT[] luts = imp.getLuts();
            if (luts == null)
                return "";
            String s = "Display ranges\n";
            int n = luts.Length;
            if (n > 7) n = 7;
            for (int i = 0; i < n; i++)
            {
                double min = luts[i].min;
                double max = luts[i].max;
                s += "  " + (i + 1) + ": " + d2s(min) + "-" + d2s(max) + "\n";
            }

            return s;
        }

        // returns a Y coordinate based on the "Invert Y Coodinates" flag
        private int yy(int y, ImagePlus imp)
        {
            return Analyzer.updateY(y, imp.getHeight());
        }

        // returns a Y coordinate based on the "Invert Y Coodinates" flag
        private double yy(double y, ImagePlus imp)
        {
            return Analyzer.updateY(y, imp.getHeight());
        }

        private void showInfo(ImagePlus imp, String info, int width, int height)
        {
            new TextWindow("Info for " + imp.getTitle(), info, width, height);
            //Editor ed = new Editor();
            //ed.setSize(width, height);
            //ed.create("Info for "+imp.getTitle(), info);
        }

        private string d2s(double n)
        {
            return IJ.d2s(n, Tools.getDecimalPlaces(n));
        }

        private string getImageProperties(ImagePlus imp)
        {
            string s = "";
            string[] props = imp.getPropertiesAsArray();
            if (props == null)
                return null;
            for (int i = 0; i < props.Length; i += 2)
            {
                string key = props[i];
                string value = props[i + 1];
                if (LUT.nameKey.Equals(key) || "UniqueName".Equals(key))
                    continue;
                if (key != null && value != null && !(key.Equals("ShowInfo") || key.Equals("Slice_Label")))
                {
                    if (value.Length < 80)
                        s += key + ": " + value + "\n";
                    else
                        s += key + ": <" + value.Length + " characters>\n";
                }
            }

            return (s.Length > 0) ? s : null;
        }
    }
}